package jmine.tec.services;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import jmine.tec.services.api.Service;
import jmine.tec.services.api.ServiceAssertionFailedError;
import jmine.tec.services.api.ServiceExecutionException;
import jmine.tec.services.api.ServicesPage;
import jmine.tec.services.api.errorlistener.ServiceErrorListener;
import jmine.tec.services.api.execution.ServicesPageExecutor;
import jmine.tec.services.api.export.ExportActor;
import jmine.tec.services.api.factory.ServiceFactory;
import jmine.tec.services.api.validation.ServiceValidator;
import jmine.tec.services.api.validation.ServicesPageValidator;
import jmine.tec.services.io.ServicesReader;
import jmine.tec.utils.uppercase.UpperCaseValueMap;
import bancosys.tec.persist.controller.AbstractPersistenceEnabledController;

/**
 * Controller do componente de serviços.
 */
public class ServicesController extends AbstractPersistenceEnabledController {

    private ServiceFactory serviceFactory;

    private List<ServiceErrorListener> serviceErrorListeners = new LinkedList<ServiceErrorListener>();

    private ServicesPageExecutor servicesPageExecutor;

    private ServicesPageExecutor dryrunExecutor;

    private ServicesPageValidator servicesPageValidator;

    private ServiceValidator serviceValidator;

    private ExportActor exportActor;

    private ServicesReader servicesReader;

    private boolean upperCase = false;

    private static final ThreadLocal<Boolean> DRY_RUN_EXECUTION = new ThreadLocal<Boolean>();

    /**
     * @return the serviceFactory
     */
    public ServiceFactory getServiceFactory() {
        return this.serviceFactory;
    }

    /**
     * @param serviceFactory the serviceFactory to set
     */
    public void setServiceFactory(ServiceFactory serviceFactory) {
        this.serviceFactory = serviceFactory;
    }

    /**
     * @return the servicesPageExecutor
     */
    public ServicesPageExecutor getServicesPageExecutor() {
        return this.servicesPageExecutor;
    }

    /**
     * @param servicesPageExecutor the servicesPageExecutor to set
     */
    public void setServicesPageExecutor(ServicesPageExecutor servicesPageExecutor) {
        this.servicesPageExecutor = servicesPageExecutor;
    }

    /**
     * @return the dryrunExecutor
     */
    public ServicesPageExecutor getDryrunExecutor() {
        return this.dryrunExecutor;
    }

    /**
     * @param dryrunExecutor the dryrunExecutor to set
     */
    public void setDryrunExecutor(ServicesPageExecutor dryrunExecutor) {
        this.dryrunExecutor = dryrunExecutor;
    }

    /**
     * @return the servicesPageValidator
     */
    public ServicesPageValidator getServicesPageValidator() {
        return this.servicesPageValidator;
    }

    /**
     * @param servicesPageValidator the servicesPageValidator to set
     */
    public void setServicesPageValidator(ServicesPageValidator servicesPageValidator) {
        this.servicesPageValidator = servicesPageValidator;
    }

    /**
     * @return the serviceValidator
     */
    public ServiceValidator getServiceValidator() {
        return this.serviceValidator;
    }

    /**
     * @param serviceValidator the serviceValidator to set
     */
    public void setServiceValidator(ServiceValidator serviceValidator) {
        this.serviceValidator = serviceValidator;
    }

    /**
     * @return the upperCase
     */
    public boolean isUpperCase() {
        return this.upperCase;
    }

    /**
     * @param upperCase the upperCase to set
     */
    public void setUpperCase(boolean upperCase) {
        this.upperCase = upperCase;
    }

    /**
     * @return the servicesReader
     */
    public ServicesReader getServicesReader() {
        return this.servicesReader;
    }

    /**
     * @param servicesReader the servicesReader to set
     */
    public void setServicesReader(ServicesReader servicesReader) {
        this.servicesReader = servicesReader;
    }

    /**
     * Factory method para centralizar a criacao dos mapas utilizados para armazenar dados.
     * 
     * @return uma nova instancia de um mapa.
     */
    public Map<String, Object> instantiateMap() {
        if (this.isUpperCase()) {
            return new UpperCaseValueMap<String, Object>();
        } else {
            return new HashMap<String, Object>();
        }
    }

    /**
     * @return the exportActor
     */
    public ExportActor getExportActor() {
        return this.exportActor;
    }

    /**
     * @param exportActor the exportActor to set
     */
    public void setExportActor(ExportActor exportActor) {
        this.exportActor = exportActor;
    }

    /**
     * Executa os serviços do arquivo (no formato de planilha - excel ou xml).
     * 
     * @param fileName o nome do arquivo
     * @param inputStream o conteúdo do arquivo
     * @throws IOException se ocorrer algum erro durante a leitura da url
     * @throws ServiceExecutionException se ocorrer algum erro durante a execução dos serviços
     */
    public void executeServices(String fileName, InputStream inputStream) throws IOException, ServiceExecutionException {
        this.executeServices(fileName, inputStream, this.getServicesPageExecutor());
    }

    /**
     * Executa os serviços do arquivo (no formato de planilha - excel ou xml) utilizando o {@link ServicesPageExecutor} informado.
     * 
     * @param fileName o nome do arquivo
     * @param inputStream o conteúdo do arquivo
     * @param executor {@link ServicesPageExecutor}
     * @throws IOException se ocorrer algum erro durante a leitura da url
     * @throws ServiceExecutionException se ocorrer algum erro durante a execução dos serviços
     */
    public void executeServices(String fileName, InputStream inputStream, ServicesPageExecutor executor) throws IOException,
            ServiceExecutionException {
        List<ServicesPage> servicesPages = this.getServicesReader().readServices(fileName, inputStream);
        executor.execute(servicesPages);
    }

    /**
     * Executa lista de serviços de um {@link ServicesPage}
     * 
     * @param servicesPage {@link ServicesPage}
     * @throws ServiceExecutionException Se ocorrer erro na execução de algum dos serviços.
     */
    public void executeServicesPage(ServicesPage servicesPage) throws ServiceExecutionException {
        ServicesPageExecutor executor;
        if (servicesPage.isDryrun()) {
            executor = this.getDryrunExecutor();
        } else {
            executor = this.getServicesPageExecutor();
        }

        boolean dryRunOldState = isDryRunExecution();
        try {
            setDryRunExecution(servicesPage.isDryrun());

            executor.execute(Collections.singletonList(servicesPage));
        } finally {
            setDryRunExecution(dryRunOldState);
        }

    }

    /**
     * Avisa os listeners registrados que ocorreu um erro durante a execução de um serviço.
     * 
     * @param service service
     * @param e erro
     */
    public void fireServiceError(Service<?> service, ServiceExecutionException e) {
        for (ServiceErrorListener listener : this.serviceErrorListeners) {
            listener.handleServiceError(service, e);
        }
    }

    /**
     * Avisa os listeners registrados que ocorreu um erro durante a execução de um serviço.
     * 
     * @param service service
     * @param e erro
     */
    public void fireServiceFailure(Service<?> service, ServiceAssertionFailedError e) {
        for (ServiceErrorListener listener : this.serviceErrorListeners) {
            listener.handleServiceFailure(service, e);
        }
    }

    /**
     * Avisa os listeners registrados que ocorreu um erro durante a execução de um serviço.
     * 
     * @param service service
     * @param t erro
     */
    public void fireServiceThrowable(Service<?> service, Throwable t) {
        for (ServiceErrorListener listener : this.serviceErrorListeners) {
            listener.handleServiceThrowable(service, t);
        }
    }

    /**
     * Retorna os serviceErrorListeners registrados
     * 
     * @return serviceErrorListeners
     */
    public List<ServiceErrorListener> getServiceErrorListeners() {
        return this.serviceErrorListeners;
    }

    /**
     * Registra um novo listener de erros de serviços.
     * 
     * @param listener listener
     */
    public void addServiceErrorListener(ServiceErrorListener listener) {
        this.serviceErrorListeners.add(listener);
    }

    /**
     * Remove o listener de erros de serviços.
     * 
     * @param listener listener
     */
    public void removeServiceErrorListener(ServiceErrorListener listener) {
        this.serviceErrorListeners.remove(listener);
    }

    /**
     * @return the dryRunExecution
     */
    public static boolean isDryRunExecution() {
        Boolean value = DRY_RUN_EXECUTION.get();
        return value == null ? false : value.booleanValue();
    }

    /**
     * @param dryRynExecution the dryRynExecution to set
     */
    public static void setDryRunExecution(boolean dryRynExecution) {
        DRY_RUN_EXECUTION.set(dryRynExecution);
    }

}
